#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using UTable.Input.MultiTouch;

namespace UTable.Objects.Controls
{
    /// <summary>
    /// Interaction logic for BookPage.xaml
    /// </summary>

    partial class UBookPage : UContentControl
    {
        public UBookPage()
        {
            InitializeComponent();
            this.InputReceived += new UTable.Input.InputEventHandler(BookPage_InputReceived);
        }

        private const int animationDuration = 500;

        private bool back = false;

        private void OnLoaded(object sender, RoutedEventArgs args)
        {
            ApplyParameters(new PageParameters(this.RenderSize));
        }

        void anim_Completed(object sender, EventArgs e)
        {
            //Application.Current.MainWindow.Title += "C";

            ApplyParameters(new PageParameters(this.RenderSize));

            if (Status == PageStatus.TurnAnimation)
            {
                Status = PageStatus.None;
                RaiseEvent(new RoutedEventArgs(UBookPage.PageTurnedEvent, this));
            }
            else
                Status = PageStatus.None;
        }

        void anim_CurrentTimeInvalidated(object sender, EventArgs e)
        {
            //Application.Current.MainWindow.Title += "I";
            PageParameters? parameters = ComputePage(this, CornerPoint, origin_point);
            _cornerPoint = CornerPoint;
            if (parameters != null)
                ApplyParameters(parameters.Value);
        }

        internal CornerOrigin origin = CornerOrigin.BottomRight;
        internal Point origin_point;
        private double gripSize = 40;
        private PageStatus _status = PageStatus.None;
        internal Action<PageStatus> SetStatus = null;
        internal Read<PageStatus> GetStatus = null;

		public double GripSize
		{
			get { return gripSize; }
			set { gripSize = value; }
		}


        public PageStatus Status
        {
            private get
            {
                if (GetStatus != null)
                    return GetStatus();
                else
                    return _status;
            }
            set
            {
                if (SetStatus != null)
                    SetStatus(value);
                else
                    _status = value;
                gridShadow.Visibility = value == PageStatus.None ? Visibility.Hidden : Visibility.Visible;
                canvasReflection.Visibility = value == PageStatus.None ? Visibility.Hidden : Visibility.Visible;
            }
        }

        private Point _cornerPoint;

        private Point CornerPoint
        {
            get { return (Point)GetValue(UBookPage.CornerPointProperty); }
            set { SetValue(UBookPage.CornerPointProperty, value); }
        }

        private void ApplyParameters(PageParameters parameters)
        {
            pageReflection.Opacity = parameters.Page0ShadowOpacity;

            rectangleRotate.Angle = parameters.Page1RotateAngle;
            rectangleRotate.CenterX = parameters.Page1RotateCenterX;
            rectangleRotate.CenterY = parameters.Page1RotateCenterY;
            rectangleTranslate.X = parameters.Page1TranslateX;
            rectangleTranslate.Y = parameters.Page1TranslateY;
            clippingFigure.Figures.Clear();
            clippingFigure.Figures.Add(parameters.Page1ClippingFigure);

            RectangleGeometry rg = (RectangleGeometry)clippingPage0.Geometry1;
            rg.Rect = new Rect(parameters.RenderSize);
            PathGeometry pg = (PathGeometry)clippingPage0.Geometry2;
            pg.Figures.Clear();
            pg.Figures.Add(parameters.Page2ClippingFigure);

            pageReflection.StartPoint = parameters.Page1ReflectionStartPoint;
            pageReflection.EndPoint = parameters.Page1ReflectionEndPoint;

            pageShadow.StartPoint = parameters.Page0ShadowStartPoint;
            pageShadow.EndPoint = parameters.Page0ShadowEndPoint;
        }


        private void BookPage_InputReceived(UTable.Input.InputEventArgs args)
        {
            if (args is UTable.Input.MultiTouch.MultiTouchEventArgs)
            {
                MultiTouchEventArgs margs = args as MultiTouchEventArgs;
                foreach (FingerEventArgs fargs in margs.FingerEvents)
                {
                    switch (fargs.EventType)
                    {
                        case FingerEventType.FINGER_DOWN_EVENT:
                            OnContactDown(fargs);
                            break;
                        case FingerEventType.FINGER_MOVE_EVENT:
                            OnContactMove(fargs);
                            break;
                        case FingerEventType.FINGER_UP_EVENT:
                            OnContactUp(fargs);
                            break;
                    }
                }
				args.Handled = true;
				args.RouteDown = false;
            }
        }

        private int _currentFingerId = -1;


        private void OnMouseMove(object sender, MouseEventArgs args)
        {
            UIElement source = sender as UIElement;
            Point p = args.GetPosition(source);
            if (Status == PageStatus.Dragging)
            {
                if (back)
                {
                    RaiseEvent(new RoutedEventArgs(UBookPage.PageBackEvent, this));
                    back = false;
                }
                PageParameters? parameters = ComputePage(source, p, origin_point);
                _cornerPoint = p;
                if (parameters != null)
                    ApplyParameters(parameters.Value);
            }
        }

        private void OnContactMove(FingerEventArgs args)
        {
            if ( _currentFingerId != args.FingerID)
                return;
            //UIElement source = sender as UIElement;
            Point p = args.Position;
            if (Status == PageStatus.Dragging)
            {
                if (back)
                {
                    RaiseEvent(new RoutedEventArgs(UBookPage.PageBackEvent, this));
                    back = false;
                }
                PageParameters? parameters = ComputePage(this, p, origin_point);
                _cornerPoint = p;
                if (parameters != null)
                    ApplyParameters(parameters.Value);
            }
            args.Handled = true;
        }

        private static int ComputeAnimationDuration(UIElement source, Point p, Point originpoint)
        {
            double ratio = ComputeProgressRatio(source, p, originpoint);
            return Convert.ToInt32(animationDuration * (ratio / 2 + 0.5));
        }
        private static double ComputeProgressRatio(UIElement source, Point p, Point origin)
        {
            if (origin.X == 0)
                return p.X / source.RenderSize.Width;
            else
                return (source.RenderSize.Width - p.X) / source.RenderSize.Width;
        }

        private void OnMouseDoubleClick(object sender, MouseButtonEventArgs args)
        {
            //Application.Current.MainWindow.Title += "D";

            //UIElement source = sender as UIElement;
            //Point p = args.GetPosition(source);

            //if (GetCorner(source, p).HasValue)
            //    TurnPage(animationDuration);
        }
        /*
        private CornerOrigin? GetCorner(UIElement source, Point position) 
        {
            CornerOrigin? result = null;

            Rect topLeftRectangle = new Rect(0, 0, gripSize, gripSize);
            Rect topRightRectangle = new Rect(source.RenderSize.Width - gripSize, 0, gripSize, gripSize);
            Rect bottomLeftRectangle = new Rect(0, source.RenderSize.Height - gripSize, gripSize, gripSize);
            Rect bottomRightRectangle = new Rect(source.RenderSize.Width - gripSize, source.RenderSize.Height - gripSize, gripSize, gripSize);

            if (IsTopLeftCornerEnabled && topLeftRectangle.Contains(position))
                result = CornerOrigin.TopLeft;
            else if (IsTopRightCornerEnabled && topRightRectangle.Contains(position))
                result = CornerOrigin.TopRight;
            else if (IsBottomLeftCornerEnabled && bottomLeftRectangle.Contains(position))
                result = CornerOrigin.BottomLeft;
            else if (IsBottomRightCornerEnabled && bottomRightRectangle.Contains(position))
                result = CornerOrigin.BottomRight;

            return result;
        }*/
        private Point? GetPoint(UIElement source, Point pos)
        {
            Point? rt = null;
            Rect left = new Rect(0, 0, gripSize, source.RenderSize.Height);
            Rect right = new Rect(source.RenderSize.Width - gripSize, 0, gripSize, source.RenderSize.Height);
            if (IsLeftSideEnabled && left.Contains(pos))
            {
                if (IsRightPage)
                {
                    back = true;
                    rt = new Point(source.RenderSize.Width, pos.Y);
                }
                else
                {
                    back = false;
                    rt = new Point(0, pos.Y);
                }
            }
            if (IsRightSideEnabled && right.Contains(pos))
            {
                back = false;
                rt = new Point(source.RenderSize.Width, pos.Y);
            }
            return rt;
        }

        private void OnMouseDown(object sender, MouseButtonEventArgs args)
        {
            if ((Status == PageStatus.DropAnimation) || (Status == PageStatus.TurnAnimation))
                return;

            UIElement source = sender as UIElement;
            Point p = args.GetPosition(source);


            Point? tmp = GetPoint(source, p);
            if (tmp.HasValue)
            {
                origin_point = (Point)tmp;
                this.CaptureMouse();
                Status = PageStatus.Dragging;
            }
        }
        private void OnContactDown(FingerEventArgs args)
        {
            if ((Status == PageStatus.DropAnimation) || (Status == PageStatus.TurnAnimation))
                return;

            //UIElement source = sender as UIElement;
            Point p = args.Position;

            Point? tmp = GetPoint(this, p);
            if (tmp.HasValue)
            {
                origin_point = (Point)tmp;
                Status = PageStatus.Dragging;
                this.CaptureFinger(args.FingerID);
                _currentFingerId = args.FingerID;
                args.Handled = true;
                (this.TemplatedParent as UBookViewer).OnPageDragging(this);
            }
        }
        private void OnMouseUp(object sender, MouseButtonEventArgs args)
        {
            if (this.IsMouseCaptured)
            {
                Status = PageStatus.None;

                UIElement source = sender as UIElement;
                Point p = args.GetPosition(source);

                if (IsOnNextPage(args.GetPosition(this), this, origin_point))
                    TurnPage(animationDuration);
                else
                    DropPage(ComputeAnimationDuration(source, p, origin_point));

                this.ReleaseMouseCapture();
            }
        }
        private void OnContactUp(FingerEventArgs args)
        {
            if (args.FingerID == _currentFingerId)
            {
                Status = PageStatus.None;

                //UIElement source = sender as UIElement;
                Point p = args.Position;

                if (IsOnNextPage(p, this, origin_point))
                    TurnPage(animationDuration);
                else
                    DropPage(ComputeAnimationDuration(this, p, origin_point));

                _currentFingerId = -1;
                this.UncaptureFinger(args.FingerID);
                args.Handled = true;
            }
        }
        private void OnMouseLeave(object sender, MouseEventArgs args)
        {/*
            if (Status == PageStatus.DraggingWithoutCapture)
            {
                //DropPage(ComputeAnimationDuration(source, p));
                //DropPage(animationDuration);
            }*/
        }

        private Point ToOppositePoint(UIElement source, Point p)
        {
            if (p.X == 0)
                return new Point(2 * source.RenderSize.Width, p.Y);
            else
                return new Point(-source.RenderSize.Width, p.Y);

        }
        private Point OriginToPoint(UIElement source, CornerOrigin origin)
        {
            switch (origin)
            {
                case CornerOrigin.BottomLeft:
                    return new Point(0, source.RenderSize.Height);
                case CornerOrigin.BottomRight:
                    return new Point(source.RenderSize.Width, source.RenderSize.Height);
                case CornerOrigin.TopRight:
                    return new Point(source.RenderSize.Width, 0);
                default:
                    return new Point(0, 0);
            }
        }
        private Point OriginToOppositePoint(UIElement source, CornerOrigin origin)
        {
            switch (origin)
            {
                case CornerOrigin.BottomLeft:
                    return new Point(source.RenderSize.Width * 2, source.RenderSize.Height);
                case CornerOrigin.BottomRight:
                    return new Point(-source.RenderSize.Width, source.RenderSize.Height);
                case CornerOrigin.TopRight:
                    return new Point(-source.RenderSize.Width, 0);
                default:
                    return new Point(source.RenderSize.Width * 2, 0);
            }
        }
        private bool IsOnNextPage(Point p, UIElement source, Point originpoint)
        {
            if (originpoint.X == 0)
                return p.X > source.RenderSize.Width / 2.0;
            else return p.X < source.RenderSize.Width / 2.0;
        }

        private void DropPage(int duration)
        {
            Status = PageStatus.DropAnimation;

            UIElement source = this as UIElement;
            CornerPoint = _cornerPoint;

            this.BeginAnimation(UBookPage.CornerPointProperty, null);
            PointAnimation anim =
                new PointAnimation(
                /*OriginToPoint(this, origin)*/origin_point,
                    new Duration(TimeSpan.FromMilliseconds(duration)));
            anim.AccelerationRatio = 0.6;

            anim.CurrentTimeInvalidated += new EventHandler(anim_CurrentTimeInvalidated);
            anim.Completed += new EventHandler(anim_Completed);
            this.BeginAnimation(UBookPage.CornerPointProperty, anim);
        }

        public void TurnPage()
        {
            TurnPage(animationDuration);
        }

        private void TurnPage(int duration)
        {
            Status = PageStatus.TurnAnimation;

            UIElement source = this as UIElement;
            CornerPoint = _cornerPoint;

            this.BeginAnimation(UBookPage.CornerPointProperty, null);
            PointAnimation anim =
                new PointAnimation(
                    ToOppositePoint(this, origin_point),
                    new Duration(TimeSpan.FromMilliseconds(duration)));
            anim.AccelerationRatio = 0.6;

            anim.CurrentTimeInvalidated += new EventHandler(anim_CurrentTimeInvalidated);
            anim.Completed += new EventHandler(anim_Completed);
            this.BeginAnimation(UBookPage.CornerPointProperty, anim);
        }

        public void AutoTurnPage(CornerOrigin fromCorner, int duration)
        {
            if (Status != PageStatus.None)
                return;

            Status = PageStatus.TurnAnimation;

            UIElement source = this as UIElement;

            this.BeginAnimation(UBookPage.CornerPointProperty, null);

            Point startPoint = OriginToPoint(this, fromCorner);
            Point endPoint = OriginToOppositePoint(this, fromCorner);

            CornerPoint = startPoint;
            origin = fromCorner;

            BezierSegment bs =
                new BezierSegment(startPoint, new Point(endPoint.X + (startPoint.X - endPoint.X) / 3, 250), endPoint, true);

            PathGeometry path = new PathGeometry();
            PathFigure figure = new PathFigure();
            figure.StartPoint = startPoint;
            figure.Segments.Add(bs);
            figure.IsClosed = false;
            path.Figures.Add(figure);

            PointAnimationUsingPath anim =
                new PointAnimationUsingPath();
            anim.PathGeometry = path;
            anim.Duration = new Duration(TimeSpan.FromMilliseconds(duration));
            anim.AccelerationRatio = 0.6;

            anim.CurrentTimeInvalidated += new EventHandler(anim_CurrentTimeInvalidated);
            anim.Completed += new EventHandler(anim_Completed);
            this.BeginAnimation(UBookPage.CornerPointProperty, anim);
        }

        public bool IsLeftSideEnabled
        {
            get { return (bool)GetValue(UBookPage.IsLeftSideEnabledProperty); }
            set { SetValue(UBookPage.IsLeftSideEnabledProperty, value); }
        }
        public bool IsRightSideEnabled
        {
            get { return (bool)GetValue(UBookPage.IsRightSideEnabledProperty); }
            set { SetValue(UBookPage.IsRightSideEnabledProperty, value); }
        }
        public bool IsRightPage
        {
            get { return (bool)GetValue(UBookPage.IsRightPageProperty); }
            set { SetValue(UBookPage.IsRightPageProperty, value); }
        }
        /*
        public bool IsTopLeftCornerEnabled 
        {
            get { return (bool) GetValue(BookPage.IsTopLeftCornerEnabledProperty) ; }
            set { SetValue(BookPage.IsTopLeftCornerEnabledProperty, value); }
        }
        public bool IsTopRightCornerEnabled 
        {
            get { return (bool) GetValue(BookPage.IsTopRightCornerEnabledProperty) ; }
            set { SetValue(BookPage.IsTopRightCornerEnabledProperty, value); }
        }
        public bool IsBottomLeftCornerEnabled 
        {
            get { return (bool) GetValue(BookPage.IsBottomLeftCornerEnabledProperty) ; }
            set { SetValue(BookPage.IsBottomLeftCornerEnabledProperty, value); }
        }
        public bool IsBottomRightCornerEnabled 
        {
            get { return (bool) GetValue(BookPage.IsBottomRightCornerEnabledProperty) ; }
            set { SetValue(BookPage.IsBottomRightCornerEnabledProperty, value); }
        }
         */

        public static DependencyProperty CornerPointProperty;


        public static DependencyProperty IsRightSideEnabledProperty;
        public static DependencyProperty IsLeftSideEnabledProperty;
        public static DependencyProperty IsRightPageProperty;

        public static readonly RoutedEvent PageTurnedEvent;
        public static readonly RoutedEvent PageBackEvent;

        public event RoutedEventHandler PageBack
        {
            add
            {
                base.AddHandler(PageBackEvent, value);
            }
            remove
            {
                base.RemoveHandler(PageBackEvent, value);
            }
        }

        public event RoutedEventHandler PageTurned
        {
            add
            {
                base.AddHandler(PageTurnedEvent, value);
            }
            remove
            {
                base.RemoveHandler(PageTurnedEvent, value);
            }
        }

        static UBookPage()
        {
            PageTurnedEvent = EventManager.RegisterRoutedEvent("PageTurned", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UBookPage));
            PageBackEvent = EventManager.RegisterRoutedEvent("PageBack", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(UBookPage));
            CornerPointProperty = DependencyProperty.Register("CornerPoint", typeof(Point), typeof(UBookPage));
            IsLeftSideEnabledProperty = DependencyProperty.Register("IsLeftSideEnabled", typeof(bool), typeof(UBookPage), new PropertyMetadata(true));
            IsRightSideEnabledProperty = DependencyProperty.Register("IsRightSideEnabled", typeof(bool), typeof(UBookPage), new PropertyMetadata(true));
            IsRightPageProperty = DependencyProperty.Register("IsRightPage", typeof(bool), typeof(UBookPage), new PropertyMetadata(true));
        }
    }

    public delegate T Read<T>();
}
